home *** CD-ROM | disk | FTP | other *** search
- Subject: v23i029: A cron/crontab replacement, Part02/02
- Newsgroups: comp.sources.unix
- Approved: rsalz@uunet.UU.NET
- X-Checksum-Snefru: 5d871866 eeecbfc3 848fc0b0 e2cdd96b
-
- Submitted-by: Paul A Vixie <vixie@vixie.sf.ca.us>
- Posting-number: Volume 23, Issue 29
- Archive-name: vixie-cron/part02
-
- #! /bin/sh
- # This is a shell archive. Remove anything before this line, then unpack
- # it by saving it into a file and typing "sh file". To overwrite existing
- # files, type "sh file -c". You can also feed this as standard input via
- # unshar, or by typing "sh <file", e.g.. If this archive is complete, you
- # will see the following message at the end:
- # "End of archive 2 (of 3)."
- # Contents: cron.h crond.c crontab.5 crontab.c database.c entry.c
- # Wrapped by vixie@volition.pa.dec.com on Wed Jul 18 00:32:48 1990
- PATH=/bin:/usr/bin:/usr/ucb ; export PATH
- if test -f 'cron.h' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'cron.h'\"
- else
- echo shar: Extracting \"'cron.h'\" \(7161 characters\)
- sed "s/^X//" >'cron.h' <<'END_OF_FILE'
- X/* cron.h - header for vixie's cron
- X *
- X * $Header: cron.h,v 2.1 90/07/18 00:23:47 vixie Exp $
- X * $Source: /jove_u3/vixie/src/cron/RCS/cron.h,v $
- X * $Revision: 2.1 $
- X * $Log: cron.h,v $
- X * Revision 2.1 90/07/18 00:23:47 vixie
- X * Baseline for 4.4BSD release
- X *
- X * Revision 2.0 88/12/10 04:57:39 vixie
- X * V2 Beta
- X *
- X * Revision 1.2 88/11/29 13:05:46 vixie
- X * seems to work on Ultrix 3.0 FT1
- X *
- X * Revision 1.1 88/11/14 12:27:49 vixie
- X * Initial revision
- X *
- X * Revision 1.4 87/05/02 17:33:08 paul
- X * baseline for mod.sources release
- X *
- X * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley]
- X * vix 30dec86 [written]
- X */
- X
- X/* Copyright 1988,1990 by Paul Vixie
- X * All rights reserved
- X *
- X * Distribute freely, except: don't remove my name from the source or
- X * documentation (don't take credit for my work), mark your changes (don't
- X * get me blamed for your possible bugs), don't alter or remove this
- X * notice. May be sold if buildable source is provided to buyer. No
- X * warrantee of any kind, express or implied, is included with this
- X * software; use at your own risk, responsibility for damages (if any) to
- X * anyone resulting from the use of this software rests entirely with the
- X * user.
- X *
- X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
- X * I'll try to keep a version up to date. I can be reached as follows:
- X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
- X * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
- X */
- X
- X#ifndef _CRON_FLAG
- X#define _CRON_FLAG
- X
- X#include <stdio.h>
- X#include <ctype.h>
- X#include <bitstring.h>
- X#include <sys/types.h>
- X#include <sys/stat.h>
- X
- X#include "config.h"
- X
- X /* these are really immutable, and are
- X * defined for symbolic convenience only
- X * TRUE, FALSE, and ERR must be distinct
- X */
- X#define TRUE 1
- X#define FALSE 0
- X /* system calls return this on success */
- X#define OK 0
- X /* or this on error */
- X#define ERR (-1)
- X
- X /* meaningless cookie for smart compilers that will pick their
- X * own register variables; this makes the code neater.
- X */
- X#define local /**/
- X
- X /* turn this on to get '-x' code */
- X#ifndef DEBUGGING
- X#define DEBUGGING FALSE
- X#endif
- X
- X#define READ_PIPE 0 /* which end of a pipe pair do you read? */
- X#define WRITE_PIPE 1 /* or write to? */
- X#define STDIN 0 /* what is stdin's file descriptor? */
- X#define STDOUT 1 /* stdout's? */
- X#define STDERR 2 /* stderr's? */
- X#define ERROR_EXIT 1 /* exit() with this will scare the shell */
- X#define OK_EXIT 0 /* exit() with this is considered 'normal' */
- X#define MAX_FNAME 100 /* max length of internally generated fn */
- X#define MAX_COMMAND 1000 /* max length of internally generated cmd */
- X#define MAX_ENVSTR 1000 /* max length of envvar=value\0 strings */
- X#define MAX_TEMPSTR 100 /* obvious */
- X#define MAX_UNAME 20 /* max length of username, should be overkill */
- X#define ROOT_UID 0 /* don't change this, it really must be root */
- X#define ROOT_USER "root" /* ditto */
- X
- X /* NOTE: these correspond to DebugFlagNames,
- X * defined below.
- X */
- X#define DEXT 0x0001 /* extend flag for other debug masks */
- X#define DSCH 0x0002 /* scheduling debug mask */
- X#define DPROC 0x0004 /* process control debug mask */
- X#define DPARS 0x0008 /* parsing debug mask */
- X#define DLOAD 0x0010 /* database loading debug mask */
- X#define DMISC 0x0020 /* misc debug mask */
- X#define DTEST 0x0040 /* test mode: don't execute any commands */
- X
- X /* the code does not depend on any of vfork's
- X * side-effects; it just uses it as a quick
- X * fork-and-exec.
- X */
- X#if defined(BSD)
- X# define VFORK vfork
- X#endif
- X#if defined(ATT)
- X# define VFORK fork
- X#endif
- X
- X#define CRON_TAB(u) "%s/%s", SPOOL_DIR, u
- X#define REG register
- X#define PPC_NULL ((char **)NULL)
- X
- X#ifndef MAXHOSTNAMELEN
- X#define MAXHOSTNAMELEN 64
- X#endif
- X
- X#define Skip_Blanks(c, f) \
- X while (c == '\t' || c == ' ') \
- X c = get_char(f);
- X
- X#define Skip_Nonblanks(c, f) \
- X while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \
- X c = get_char(f);
- X
- X#define Skip_Line(c, f) \
- X do {c = get_char(f);} while (c != '\n' && c != EOF);
- X
- X#if DEBUGGING
- X# define Debug(mask, message) \
- X if ( (DebugFlags & (mask) ) == (mask) ) \
- X printf message;
- X#else /* !DEBUGGING */
- X# define Debug(mask, message) \
- X ;
- X#endif /* DEBUGGING */
- X
- X#define MkLower(ch) (isupper(ch) ? tolower(ch) : ch)
- X#define MkUpper(ch) (islower(ch) ? toupper(ch) : ch)
- X#define Set_LineNum(ln) {Debug(DPARS|DEXT,("linenum=%d\n",ln)); \
- X LineNumber = ln; \
- X }
- X
- X#define FIRST_MINUTE 0
- X#define LAST_MINUTE 59
- X#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1)
- X
- X#define FIRST_HOUR 0
- X#define LAST_HOUR 23
- X#define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1)
- X
- X#define FIRST_DOM 1
- X#define LAST_DOM 31
- X#define DOM_COUNT (LAST_DOM - FIRST_DOM + 1)
- X
- X#define FIRST_MONTH 1
- X#define LAST_MONTH 12
- X#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1)
- X
- X/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */
- X#define FIRST_DOW 0
- X#define LAST_DOW 7
- X#define DOW_COUNT (LAST_DOW - FIRST_DOW + 1)
- X
- X /* each user's crontab will be held as a list of
- X * the following structure.
- X *
- X * These are the cron commands.
- X */
- X
- typedef struct _entry
- X {
- X struct _entry *next;
- X char *cmd;
- X bitstr_t bit_decl(minute, MINUTE_COUNT);
- X bitstr_t bit_decl(hour, HOUR_COUNT);
- X bitstr_t bit_decl(dom, DOM_COUNT);
- X bitstr_t bit_decl(month, MONTH_COUNT);
- X bitstr_t bit_decl(dow, DOW_COUNT);
- X int flags;
- X# define DOM_STAR 0x1
- X# define DOW_STAR 0x2
- X# define WHEN_REBOOT 0x4
- X }
- X entry;
- X
- X /* the crontab database will be a list of the
- X * following structure, one element per user.
- X *
- X * These are the crontabs.
- X */
- X
- typedef struct _user
- X {
- X struct _user *next, *prev; /* links */
- X int uid; /* uid from passwd file */
- X int gid; /* gid from passwd file */
- X char **envp; /* environ for commands */
- X time_t mtime; /* last modtime of crontab */
- X entry *crontab; /* this person's crontab */
- X }
- X user;
- X
- typedef struct _cron_db
- X {
- X user *head, *tail; /* links */
- X time_t mtime; /* last modtime on spooldir */
- X }
- X cron_db;
- X
- X /* in the C tradition, we only create
- X * variables for the main program, just
- X * extern them elsewhere.
- X */
- X
- X#ifdef MAIN_PROGRAM
- X
- X# if !defined(LINT) && !defined(lint)
- X static char *copyright[] = {
- X "@(#) Copyright (C) 1988, 1989, 1990 by Paul Vixie",
- X "@(#) All rights reserved"
- X };
- X# endif
- X
- X char *MonthNames[] = {
- X "Jan", "Feb", "Mar", "Apr", "May", "Jun",
- X "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
- X NULL};
- X char *DowNames[] = {
- X "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
- X NULL};
- X char *ProgramName;
- X int LineNumber;
- X time_t TargetTime;
- X
- X# if DEBUGGING
- X int DebugFlags;
- X char *DebugFlagNames[] = { /* sync with #defines */
- X "ext", "sch", "proc", "pars", "load", "misc", "test",
- X NULL}; /* NULL must be last element */
- X# endif /* DEBUGGING */
- X
- X#else /* !MAIN_PROGRAM */
- X
- X extern char *MonthNames[];
- X extern char *DowNames[];
- X extern char *ProgramName;
- X extern int LineNumber;
- X extern time_t TargetTime;
- X# if DEBUGGING
- X extern int DebugFlags;
- X extern char *DebugFlagNames[];
- X# endif /* DEBUGGING */
- X#endif /* MAIN_PROGRAM */
- X
- X
- X#endif /* _CRON_FLAG */
- END_OF_FILE
- if test 7161 -ne `wc -c <'cron.h'`; then
- echo shar: \"'cron.h'\" unpacked with wrong size!
- fi
- # end of 'cron.h'
- fi
- if test -f 'crond.c' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'crond.c'\"
- else
- echo shar: Extracting \"'crond.c'\" \(7067 characters\)
- sed "s/^X//" >'crond.c' <<'END_OF_FILE'
- X#if !defined(lint) && !defined(LINT)
- static char rcsid[] = "$Header: crond.c,v 2.1 90/07/18 00:23:53 vixie Exp $";
- X#endif
- X
- X/* Copyright 1988,1990 by Paul Vixie
- X * All rights reserved
- X *
- X * Distribute freely, except: don't remove my name from the source or
- X * documentation (don't take credit for my work), mark your changes (don't
- X * get me blamed for your possible bugs), don't alter or remove this
- X * notice. May be sold if buildable source is provided to buyer. No
- X * warrantee of any kind, express or implied, is included with this
- X * software; use at your own risk, responsibility for damages (if any) to
- X * anyone resulting from the use of this software rests entirely with the
- X * user.
- X *
- X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
- X * I'll try to keep a version up to date. I can be reached as follows:
- X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
- X * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
- X */
- X
- X
- X#define MAIN_PROGRAM
- X
- X
- X#include "cron.h"
- X#include <sys/time.h>
- X#include <sys/signal.h>
- X#include <sys/types.h>
- X#if defined(BSD)
- X# include <sys/wait.h>
- X# include <sys/resource.h>
- X#endif /*BSD*/
- X
- extern int fprintf(), fork(), unlink();
- extern time_t time();
- extern void exit();
- extern unsigned sleep();
- X
- void
- usage()
- X{
- X (void) fprintf(stderr, "usage: %s [-x debugflag[,...]]\n", ProgramName);
- X (void) exit(ERROR_EXIT);
- X}
- X
- X
- int
- main(argc, argv)
- X int argc;
- X char *argv[];
- X{
- X extern void set_cron_uid(), be_different(), load_database(),
- X set_cron_cwd(), open_logfile();
- X
- X static void cron_tick(), cron_sleep(), cron_sync(),
- X sigchld_handler(), parse_args(), run_reboot_jobs();
- X
- X auto cron_db database;
- X
- X ProgramName = argv[0];
- X
- X#if defined(BSD)
- X setlinebuf(stdout);
- X setlinebuf(stderr);
- X#endif
- X
- X parse_args(argc, argv);
- X
- X# if DEBUGGING
- X /* if there are no debug flags turned on, fork as a daemon should.
- X */
- X if (DebugFlags)
- X {
- X (void) fprintf(stderr, "[%d] crond started\n", getpid());
- X }
- X else
- X# endif /*DEBUGGING*/
- X {
- X switch (fork())
- X {
- X case -1:
- X log_it("CROND",getpid(),"DEATH","can't fork");
- X exit(0);
- X break;
- X case 0:
- X /* child process */
- X be_different();
- X break;
- X default:
- X /* parent process should just die */
- X _exit(0);
- X }
- X }
- X
- X#if defined(BSD)
- X (void) signal(SIGCHLD, sigchld_handler);
- X#endif /*BSD*/
- X
- X#if defined(ATT)
- X (void) signal(SIGCLD, SIG_IGN);
- X#endif /*ATT*/
- X
- X acquire_daemonlock();
- X set_cron_uid();
- X set_cron_cwd();
- X database.head = NULL;
- X database.tail = NULL;
- X database.mtime = (time_t) 0;
- X load_database(&database);
- X run_reboot_jobs(&database);
- X cron_sync();
- X while (TRUE)
- X {
- X# if DEBUGGING
- X if (!(DebugFlags & DTEST))
- X# endif /*DEBUGGING*/
- X cron_sleep();
- X
- X load_database(&database);
- X
- X /* do this iteration
- X */
- X cron_tick(&database);
- X
- X /* sleep 1 minute
- X */
- X TargetTime += 60;
- X }
- X}
- X
- X
- static void
- run_reboot_jobs(db)
- X cron_db *db;
- X{
- X extern void job_add();
- X extern int job_runqueue();
- X register user *u;
- X register entry *e;
- X
- X for (u = db->head; u != NULL; u = u->next) {
- X for (e = u->crontab; e != NULL; e = e->next) {
- X if (e->flags & WHEN_REBOOT) {
- X job_add(e->cmd, u);
- X }
- X }
- X }
- X (void) job_runqueue();
- X}
- X
- X
- static void
- cron_tick(db)
- X cron_db *db;
- X{
- X extern void job_add();
- X extern char *env_get();
- X extern struct tm *localtime();
- X register struct tm *tm = localtime(&TargetTime);
- X local int minute, hour, dom, month, dow;
- X register user *u;
- X register entry *e;
- X
- X /* make 0-based values out of these so we can use them as indicies
- X */
- X minute = tm->tm_min -FIRST_MINUTE;
- X hour = tm->tm_hour -FIRST_HOUR;
- X dom = tm->tm_mday -FIRST_DOM;
- X month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
- X dow = tm->tm_wday -FIRST_DOW;
- X
- X Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n",
- X getpid(), minute, hour, dom, month, dow))
- X
- X /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
- X * first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
- X * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
- X * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre.
- X * like many bizarre things, it's the standard.
- X */
- X for (u = db->head; u != NULL; u = u->next) {
- X Debug(DSCH|DEXT, ("user [%s:%d:%d:...]\n",
- X env_get(USERENV,u->envp), u->uid, u->gid))
- X for (e = u->crontab; e != NULL; e = e->next) {
- X Debug(DSCH|DEXT, ("entry [%s]\n", e->cmd))
- X if (bit_test(e->minute, minute)
- X && bit_test(e->hour, hour)
- X && bit_test(e->month, month)
- X && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
- X ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
- X : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
- X )
- X ) {
- X job_add(e->cmd, u);
- X }
- X }
- X }
- X}
- X
- X
- X/* the task here is to figure out how long it's going to be until :00 of the
- X * following minute and initialize TargetTime to this value. TargetTime
- X * will subsequently slide 60 seconds at a time, with correction applied
- X * implicitly in cron_sleep(). it would be nice to let crond execute in
- X * the "current minute" before going to sleep, but by restarting cron you
- X * could then get it to execute a given minute's jobs more than once.
- X * instead we have the chance of missing a minute's jobs completely, but
- X * that's something sysadmin's know to expect what with crashing computers..
- X */
- static void
- cron_sync()
- X{
- X extern struct tm *localtime();
- X register struct tm *tm;
- X
- X TargetTime = time((time_t*)0);
- X tm = localtime(&TargetTime);
- X TargetTime += (60 - tm->tm_sec);
- X}
- X
- X
- static void
- cron_sleep()
- X{
- X extern void do_command();
- X extern int job_runqueue();
- X register int seconds_to_wait;
- X
- X do {
- X seconds_to_wait = (int) (TargetTime - time((time_t*)0));
- X Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
- X getpid(), TargetTime, seconds_to_wait))
- X
- X /* if we intend to sleep, this means that it's finally
- X * time to empty the job queue (execute it).
- X *
- X * if we run any jobs, we'll probably screw up our timing,
- X * so go recompute.
- X *
- X * note that we depend here on the left-to-right nature
- X * of &&, and the short-circuiting.
- X */
- X } while (seconds_to_wait > 0 && job_runqueue());
- X
- X if (seconds_to_wait > 0)
- X {
- X Debug(DSCH, ("[%d] sleeping for %d seconds\n",
- X getpid(), seconds_to_wait))
- X (void) sleep((unsigned int) seconds_to_wait);
- X }
- X}
- X
- X
- X#if defined(BSD)
- static void
- sigchld_handler()
- X{
- X union wait waiter;
- X int pid;
- X
- X for (;;)
- X {
- X pid = wait3(&waiter, WNOHANG, (struct rusage *)0);
- X switch (pid)
- X {
- X case -1:
- X Debug(DPROC,
- X ("[%d] sigchld...no children\n", getpid()))
- X return;
- X case 0:
- X Debug(DPROC,
- X ("[%d] sigchld...no dead kids\n", getpid()))
- X return;
- X default:
- X Debug(DPROC,
- X ("[%d] sigchld...pid #%d died, stat=%d\n",
- X getpid(), pid, waiter.w_status))
- X }
- X }
- X}
- X#endif /*BSD*/
- X
- X
- static void
- parse_args(argc, argv)
- X int argc;
- X char *argv[];
- X{
- X extern int optind, getopt();
- X extern void usage();
- X extern char *optarg;
- X
- X int argch;
- X
- X while (EOF != (argch = getopt(argc, argv, "x:")))
- X {
- X switch (argch)
- X {
- X default:
- X usage();
- X case 'x':
- X if (!set_debug_flags(optarg))
- X usage();
- X break;
- X }
- X }
- X}
- END_OF_FILE
- if test 7067 -ne `wc -c <'crond.c'`; then
- echo shar: \"'crond.c'\" unpacked with wrong size!
- fi
- # end of 'crond.c'
- fi
- if test -f 'crontab.5' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'crontab.5'\"
- else
- echo shar: Extracting \"'crontab.5'\" \(7270 characters\)
- sed "s/^X//" >'crontab.5' <<'END_OF_FILE'
- X.\" $Header: crontab.5,v 2.1 90/07/18 00:23:50 vixie Exp $
- X.\"
- X.\"/* Copyright 1988,1990 by Paul Vixie
- X.\" * All rights reserved
- X.\" *
- X.\" * Distribute freely, except: don't remove my name from the source or
- X.\" * documentation (don't take credit for my work), mark your changes (don't
- X.\" * get me blamed for your possible bugs), don't alter or remove this
- X.\" * notice. May be sold if buildable source is provided to buyer. No
- X.\" * warrantee of any kind, express or implied, is included with this
- X.\" * software; use at your own risk, responsibility for damages (if any) to
- X.\" * anyone resulting from the use of this software rests entirely with the
- X.\" * user.
- X.\" *
- X.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
- X.\" * I'll try to keep a version up to date. I can be reached as follows:
- X.\" * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
- X.\" * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
- X.\" */
- X.TH CRONTAB 5 "15 January 1990"
- X.UC 4
- X.SH NAME
- crontab \- tables for driving cron
- X.SH DESCRIPTION
- A
- X.I crontab
- file contains instructions to the
- X.IR crond (8)
- daemon of the general form: ``run this command at this time on this date''.
- XEach user has their own crontab, and commands in any given crontab will be
- executed as the user who owns the crontab. Uucp and News will usually have
- their own crontabs, eliminating the need for explicitly running
- X.IR su (1)
- as part of a cron command.
- X.PP
- Blank lines and leading spaces and tabs are ignored. Lines whose first
- non-space character is a pound-sign (#) are comments, and are ignored.
- Note that comments are not allowed on the same line as cron commands, since
- they will be taken to be part of the command. Similarly, comments are not
- allowed on the same line as environment variable settings.
- X.PP
- An active line in a crontab will be either an environment setting or a cron
- command. An environment setting is of the form,
- X.PP
- X name = value
- X.PP
- where the spaces around the equal-sign (=) are optional, and any subsequent
- non-leading spaces in
- X.I value
- will be part of the value assigned to
- X.IR name .
- The
- X.I value
- string may be placed in quotes (single or double, but matching) to preserve
- leading or trailing blanks.
- X.PP
- Several environment variables are set up
- automatically by the
- X.IR crond (8)
- daemon from the /etc/passwd line of the crontab's owner: USER, HOME, and SHELL.
- HOME and SHELL may be overridden by settings in the crontab; USER may not.
- X.PP
- X(Note: for UUCP, always set SHELL=/bin/sh, or
- X.IR crond (8)
- will cheerfully try to execute your commands using /usr/lib/uucp/uucico.)
- X.PP
- X(Another note: the USER variable is sometimes called LOGNAME or worse on
- System V... on these systems, LOGNAME will be set rather than USER.)
- X.PP
- In addition to USER, HOME, and SHELL,
- X.IR crond (8)
- will look at MAILTO if it has any reason to send mail as a result of running
- commands in ``this'' crontab. If MAILTO is defined (and non-empty), mail is
- sent to the user so named. If MAILTO is defined but empty (MAILTO=""), no
- mail will be sent. Otherwise mail is sent to the owner of the crontab. This
- option is useful if you decide on /bin/mail instead of /usr/lib/sendmail as
- your mailer when you install cron -- /bin/mail doesn't do aliasing, and UUCP
- usually doesn't read its mail.
- X.PP
- The format of a cron command is very much the V7 standard, with a number of
- upward-compatible extensions. Each line has five time and date fields,
- followed by a command. Commands are executed by
- X.IR crond (8)
- when the minute, hour, and month of year fields match the current time,
- X.I and
- when at least one of the two day fields (day of month, or day of week)
- match the current time (see ``Note'' below).
- X.IR crond (8)
- examines cron entries once every minute.
- The time and date fields are:
- X.IP
- X.ta 1.5i
- field allowed values
- X.br
- X----- --------------
- X.br
- minute 0-59
- X.br
- hour 0-23
- X.br
- day of month 0-31
- X.br
- month 0-12 (or names, see below)
- X.br
- day of week 0-7 (0 or 7 is Sun, or use names)
- X.br
- X.PP
- A field may be an asterisk (*), which always matches the
- current time.
- X.PP
- Ranges of numbers are allowed. Ranges are two numbers separated
- with a hyphen. The specified range is inclusive. For example,
- X8-11 for an ``hours'' entry specifies execution at hours 8, 9, 10
- and 11.
- X.PP
- Lists are allowed. A list is a set of numbers (or ranges)
- separated by commas. Examples: ``1,2,5,9'', ``0-4,8-12''.
- X.PP
- Step values can be used in conjunction with ranges. Following
- a range with ``/<number>'' specifies skips of the number's value
- through the range. For example, ``0-23/2'' can be used in the hours
- field to specify command execution every other hour (the alternative
- in the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22'').
- X.PP
- Names can also be used for the ``month'' and ``day of week''
- fields. Use the first three letters of the particular
- day or month (case doesn't matter). Ranges or
- lists of names are not allowed.
- X.PP
- The ``sixth'' field (the rest of the line) specifies the command to be
- run.
- The entire command portion of the line, up to a newline or %
- character, will be executed by the user's login shell or by the shell
- specified in the SHELL variable of the cronfile.
- Percent-signs (%) in the command, unless escaped with backslash
- X(\\), will be changed into newline characters, and all data
- after the first % will be sent to the command as standard
- input.
- X.PP
- Note: The day of a command's execution can be specified by two
- fields \(em day of month, and day of week. If both fields are
- restricted (ie, aren't *), the command will be run when
- X.I either
- field matches the current time. For example,
- X.br
- X``30 4 1,15 * 5''
- would cause a command to be run at 4:30 am on the 1st and 15th of each
- month, plus every Friday.
- X.SH EXAMPLE CRON FILE
- X.nf
- X
- X# use /bin/sh to run commands, no matter what /etc/passwd says
- SHELL=/bin/sh
- X# mail any output to `paul', no matter whose crontab this is
- MAILTO=paul
- X#
- X# run five minutes after midnight, every day
- X5 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&1
- X# run at 2:15pm on the first of every month -- output mailed to paul
- X15 14 1 * * $HOME/bin/monthly
- X# run at 10 pm on weekdays, annoy Joe
- X0 22 * * 1-5 mail -s "It's 10pm" joe%Joe,%%Where are your kids?%
- X23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday"
- X5 4 * * sun echo "run at 5 after 4 every sunday"
- X.fi
- X.SH SEE ALSO
- crond(8), crontab(1)
- X.SH EXTENSIONS
- When specifying day of week, both day 0 and day 7 will be considered Sunday.
- BSD and ATT seem to disagree about this.
- X.PP
- Lists and ranges are allowed to co-exist in the same field. "1-3,7-9" would
- be rejected by ATT or BSD cron -- they want to see "1-3" or "7,8,9" ONLY.
- X.PP
- Ranges can include "steps", so "1-9/2" is the same as "1,3,5,7,9".
- X.PP
- Names of months or days of the week can be specified by name.
- X.PP
- XEnvironment variables can be set in the crontab. In BSD or ATT, the
- environment handed to child processes is basically the one from /etc/rc.
- X.PP
- Command output is mailed to the crontab owner (BSD can't do this), can be
- mailed to a person other than the crontab owner (SysV can't do this), or the
- feature can be turned off and no mail will be sent at all (SysV can't do this
- either).
- X.SH AUTHOR
- X.nf
- Paul Vixie, paul@vixie.sf.ca.us
- END_OF_FILE
- if test 7270 -ne `wc -c <'crontab.5'`; then
- echo shar: \"'crontab.5'\" unpacked with wrong size!
- fi
- # end of 'crontab.5'
- fi
- if test -f 'crontab.c' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'crontab.c'\"
- else
- echo shar: Extracting \"'crontab.c'\" \(8715 characters\)
- sed "s/^X//" >'crontab.c' <<'END_OF_FILE'
- X#if !defined(lint) && !defined(LINT)
- static char rcsid[] = "$Header: crontab.c,v 2.2 90/07/18 00:23:56 vixie Exp $";
- X#endif
- X
- X/* Revision 1.5 87/05/02 17:33:22 paul
- X * pokecron? (RCS file has the rest of the log)
- X *
- X * Revision 1.5 87/05/02 17:33:22 paul
- X * baseline for mod.sources release
- X *
- X * Revision 1.4 87/03/31 13:11:48 paul
- X * I won't say that rs@mirror gave me this idea but crontab uses getopt() now
- X *
- X * Revision 1.3 87/03/30 23:43:48 paul
- X * another suggestion from rs@mirror:
- X * use getpwuid(getuid)->pw_name instead of getenv("USER")
- X * this is a boost to security...
- X *
- X * Revision 1.2 87/02/11 17:40:12 paul
- X * changed command syntax to allow append and replace instead of append as
- X * default and no replace at all.
- X *
- X * Revision 1.1 87/01/26 23:49:06 paul
- X * Initial revision
- X */
- X
- X/* Copyright 1988,1990 by Paul Vixie
- X * All rights reserved
- X *
- X * Distribute freely, except: don't remove my name from the source or
- X * documentation (don't take credit for my work), mark your changes (don't
- X * get me blamed for your possible bugs), don't alter or remove this
- X * notice. May be sold if buildable source is provided to buyer. No
- X * warrantee of any kind, express or implied, is included with this
- X * software; use at your own risk, responsibility for damages (if any) to
- X * anyone resulting from the use of this software rests entirely with the
- X * user.
- X *
- X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
- X * I'll try to keep a version up to date. I can be reached as follows:
- X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
- X * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
- X */
- X
- X
- X#define MAIN_PROGRAM
- X
- X
- X#include "cron.h"
- X#include <pwd.h>
- X#include <errno.h>
- X#include <sys/file.h>
- X#if defined(BSD)
- X# include <sys/time.h>
- X#endif /*BSD*/
- X
- extern char *sprintf();
- X
- X
- static int Pid;
- static char User[MAX_UNAME], RealUser[MAX_UNAME];
- static char Filename[MAX_FNAME];
- static FILE *NewCrontab;
- static int CheckErrorCount;
- static enum {opt_unknown, opt_list, opt_delete, opt_replace}
- X Option;
- X
- extern void log_it();
- X
- X#if DEBUGGING
- static char *Options[] = {"???", "list", "delete", "replace"};
- X#endif
- X
- static void
- usage()
- X{
- X fprintf(stderr, "usage: %s [-u user] ...\n", ProgramName);
- X fprintf(stderr, " ... -l (list user's crontab)\n");
- X fprintf(stderr, " ... -d (delete user's crontab)\n");
- X fprintf(stderr, " ... -r file (replace user's crontab)\n");
- X exit(ERROR_EXIT);
- X}
- X
- X
- main(argc, argv)
- X int argc;
- X char *argv[];
- X{
- X void parse_args(), set_cron_uid(), set_cron_cwd(),
- X list_cmd(), delete_cmd(), replace_cmd();
- X
- X Pid = getpid();
- X ProgramName = argv[0];
- X#if defined(BSD)
- X setlinebuf(stderr);
- X#endif
- X parse_args(argc, argv); /* sets many globals, opens a file */
- X set_cron_uid();
- X set_cron_cwd();
- X if (!allowed(User)) {
- X fprintf(stderr,
- X "You (%s) are not allowed to use this program (%s)\n",
- X User, ProgramName);
- X fprintf(stderr, "See crontab(1) for more information\n");
- X log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
- X exit(ERROR_EXIT);
- X }
- X switch (Option)
- X {
- X case opt_list: list_cmd();
- X break;
- X case opt_delete: delete_cmd();
- X break;
- X case opt_replace: replace_cmd();
- X break;
- X }
- X}
- X
- X
- static void
- parse_args(argc, argv)
- X int argc;
- X char *argv[];
- X{
- X void usage();
- X char *getenv(), *strcpy();
- X int getuid();
- X struct passwd *getpwnam();
- X extern int getopt(), optind;
- X extern char *optarg;
- X
- X struct passwd *pw;
- X int argch;
- X
- X if (!(pw = getpwuid(getuid())))
- X {
- X fprintf(stderr, "%s: your UID isn't in the passwd file.\n",
- X ProgramName);
- X fprintf(stderr, "bailing out.\n");
- X exit(ERROR_EXIT);
- X }
- X strcpy(User, pw->pw_name);
- X strcpy(RealUser, User);
- X Filename[0] = '\0';
- X Option = opt_unknown;
- X while (EOF != (argch = getopt(argc, argv, "u:ldr:x:")))
- X {
- X switch (argch)
- X {
- X case 'x':
- X if (!set_debug_flags(optarg))
- X usage();
- X break;
- X case 'u':
- X if (getuid() != ROOT_UID)
- X {
- X fprintf(stderr,
- X "must be privileged to use -u\n");
- X exit(ERROR_EXIT);
- X }
- X if ((struct passwd *)NULL == getpwnam(optarg))
- X {
- X fprintf(stderr, "%s: user `%s' unknown\n",
- X ProgramName, optarg);
- X exit(ERROR_EXIT);
- X }
- X (void) strcpy(User, optarg);
- X break;
- X case 'l':
- X if (Option != opt_unknown)
- X usage();
- X Option = opt_list;
- X break;
- X case 'd':
- X if (Option != opt_unknown)
- X usage();
- X Option = opt_delete;
- X break;
- X case 'r':
- X if (Option != opt_unknown)
- X usage();
- X Option = opt_replace;
- X (void) strcpy(Filename, optarg);
- X break;
- X default:
- X usage();
- X }
- X }
- X
- X endpwent();
- X
- X if (Option == opt_unknown || argv[optind] != NULL)
- X usage();
- X
- X if (Option == opt_replace) {
- X if (!Filename[0]) {
- X /* getopt(3) says this can't be true
- X * but I'm paranoid today.
- X */
- X fprintf(stderr, "filename must be given for -a or -r\n");
- X usage();
- X }
- X /* we have to open the file here because we're going to
- X * chdir(2) into /var/cron before we get around to
- X * reading the file.
- X */
- X if (!strcmp(Filename, "-")) {
- X NewCrontab = stdin;
- X } else {
- X if (!(NewCrontab = fopen(Filename, "r"))) {
- X perror(Filename);
- X exit(ERROR_EXIT);
- X }
- X }
- X }
- X
- X Debug(DMISC, ("user=%s, file=%s, option=%s\n",
- X User, Filename, Options[(int)Option]))
- X}
- X
- X
- static void
- list_cmd()
- X{
- X extern errno;
- X char n[MAX_FNAME];
- X FILE *f;
- X int ch;
- X
- X log_it(RealUser, Pid, "LIST", User);
- X (void) sprintf(n, CRON_TAB(User));
- X if (!(f = fopen(n, "r")))
- X {
- X if (errno == ENOENT)
- X fprintf(stderr, "no crontab for %s\n", User);
- X else
- X perror(n);
- X exit(ERROR_EXIT);
- X }
- X
- X /* file is open. copy to stdout, close.
- X */
- X Set_LineNum(1)
- X while (EOF != (ch = get_char(f)))
- X putchar(ch);
- X fclose(f);
- X}
- X
- X
- static void
- delete_cmd()
- X{
- X extern errno;
- X int unlink();
- X void poke_daemon();
- X char n[MAX_FNAME];
- X
- X log_it(RealUser, Pid, "DELETE", User);
- X (void) sprintf(n, CRON_TAB(User));
- X if (unlink(n))
- X {
- X if (errno == ENOENT)
- X fprintf(stderr, "no crontab for %s\n", User);
- X else
- X perror(n);
- X exit(ERROR_EXIT);
- X }
- X poke_daemon();
- X}
- X
- X
- static void
- check_error(msg)
- X char *msg;
- X{
- X CheckErrorCount += 1;
- X fprintf(stderr, "\"%s\", line %d: %s\n", Filename, LineNumber, msg);
- X}
- X
- X
- static void
- replace_cmd()
- X{
- X char *sprintf();
- X entry *load_entry();
- X int load_env();
- X int unlink();
- X void free_entry();
- X void check_error();
- X void poke_daemon();
- X extern errno;
- X
- X char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
- X FILE *tmp;
- X int ch;
- X entry *e;
- X int status;
- X time_t now = time(NULL);
- X
- X (void) sprintf(n, "tmp.%d", Pid);
- X (void) sprintf(tn, CRON_TAB(n));
- X if (!(tmp = fopen(tn, "w"))) {
- X perror(tn);
- X exit(ERROR_EXIT);
- X }
- X
- X /* write a signature at the top of the file. for brian.
- X */
- X fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
- X fprintf(tmp, "# (Cron version -- %s)\n", rcsid);
- X
- X /* copy the crontab to the tmp
- X */
- X Set_LineNum(1)
- X while (EOF != (ch = get_char(NewCrontab)))
- X putc(ch, tmp);
- X fclose(NewCrontab);
- X fflush(tmp); rewind(tmp);
- X
- X if (ferror(tmp)) {
- X fprintf("%s: error while writing new crontab to %s\n",
- X ProgramName, tn);
- X fclose(tmp); unlink(tn);
- X exit(ERROR_EXIT);
- X }
- X
- X /* check the syntax of the file being installed.
- X */
- X
- X /* BUG: was reporting errors after the EOF if there were any errors
- X * in the file proper -- kludged it by stopping after first error.
- X * vix 31mar87
- X */
- X CheckErrorCount = 0;
- X while (!CheckErrorCount && (status = load_env(envstr, tmp)) >= OK)
- X {
- X if (status == FALSE)
- X {
- X if (NULL != (e = load_entry(NewCrontab, check_error)))
- X free((char *) e);
- X }
- X }
- X
- X if (CheckErrorCount != 0)
- X {
- X fprintf(stderr, "errors in crontab file, can't install.\n");
- X fclose(tmp); unlink(tn);
- X exit(ERROR_EXIT);
- X }
- X
- X if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
- X {
- X perror("chown");
- X fclose(tmp); unlink(tn);
- X exit(ERROR_EXIT);
- X }
- X
- X if (fchmod(fileno(tmp), 0600) < OK)
- X {
- X perror("chown");
- X fclose(tmp); unlink(tn);
- X exit(ERROR_EXIT);
- X }
- X
- X if (fclose(tmp) == EOF) {
- X perror("fclose");
- X unlink(tn);
- X exit(ERROR_EXIT);
- X }
- X
- X (void) sprintf(n, CRON_TAB(User));
- X if (rename(tn, n))
- X {
- X fprintf(stderr, "%s: error renaming %s to %s\n",
- X ProgramName, tn, n);
- X perror("rename");
- X unlink(tn);
- X exit(ERROR_EXIT);
- X }
- X log_it(RealUser, Pid, "REPLACE", User);
- X
- X poke_daemon();
- X}
- X
- X
- static void
- poke_daemon()
- X{
- X#if defined(BSD)
- X struct timeval tvs[2];
- X struct timezone tz;
- X
- X (void) gettimeofday(&tvs[0], &tz);
- X tvs[1] = tvs[0];
- X if (utimes(SPOOL_DIR, tvs) < OK)
- X {
- X fprintf(stderr, "crontab: can't update mtime on spooldir\n");
- X perror(SPOOL_DIR);
- X return;
- X }
- X#endif /*BSD*/
- X#if defined(ATT)
- X if (utime(SPOOL_DIR, NULL) < OK)
- X {
- X fprintf(stderr, "crontab: can't update mtime on spooldir\n");
- X perror(SPOOL_DIR);
- X return;
- X }
- X#endif /*ATT*/
- X}
- END_OF_FILE
- if test 8715 -ne `wc -c <'crontab.c'`; then
- echo shar: \"'crontab.c'\" unpacked with wrong size!
- fi
- # end of 'crontab.c'
- fi
- if test -f 'database.c' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'database.c'\"
- else
- echo shar: Extracting \"'database.c'\" \(6768 characters\)
- sed "s/^X//" >'database.c' <<'END_OF_FILE'
- X#if !defined(lint) && !defined(LINT)
- static char rcsid[] = "$Header: database.c,v 2.1 90/07/18 00:23:51 vixie Exp $";
- X#endif
- X
- X/* vix 26jan87 [RCS has the log]
- X */
- X
- X/* Copyright 1988,1990 by Paul Vixie
- X * All rights reserved
- X *
- X * Distribute freely, except: don't remove my name from the source or
- X * documentation (don't take credit for my work), mark your changes (don't
- X * get me blamed for your possible bugs), don't alter or remove this
- X * notice. May be sold if buildable source is provided to buyer. No
- X * warrantee of any kind, express or implied, is included with this
- X * software; use at your own risk, responsibility for damages (if any) to
- X * anyone resulting from the use of this software rests entirely with the
- X * user.
- X *
- X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
- X * I'll try to keep a version up to date. I can be reached as follows:
- X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
- X * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
- X */
- X
- X
- X#include "cron.h"
- X#include <pwd.h>
- X#if defined(BSD)
- X# include <sys/file.h>
- X# include <sys/dir.h>
- X#endif
- X#if defined(ATT)
- X# include <sys/file.h>
- X# include <ndir.h>
- X# include <fcntl.h>
- X#endif
- X
- X
- extern void perror(), exit();
- X
- X
- void
- load_database(old_db)
- X cron_db *old_db;
- X{
- X extern void link_user(), unlink_user(), free_user();
- X extern user *load_user(), *find_user();
- X extern char *env_get();
- X
- X static DIR *dir = NULL;
- X
- X struct stat statbuf;
- X struct direct *dp;
- X cron_db new_db;
- X user *u;
- X
- X Debug(DLOAD, ("[%d] load_database()\n", getpid()))
- X
- X /* before we start loading any data, do a stat on SPOOL_DIR
- X * so that if anything changes as of this moment (i.e., before we've
- X * cached any of the database), we'll see the changes next time.
- X */
- X if (stat(SPOOL_DIR, &statbuf) < OK)
- X {
- X log_it("CROND", getpid(), "STAT FAILED", SPOOL_DIR);
- X (void) exit(ERROR_EXIT);
- X }
- X
- X /* if spooldir's mtime has not changed, we don't need to fiddle with
- X * the database. Note that if /etc/passwd changes (like, someone's
- X * UID/GID/HOME/SHELL, we won't see it. Maybe we should
- X * keep an mtime for the passwd file? HINT
- X *
- X * Note that old_db->mtime is initialized to 0 in main(), and
- X * so is guaranteed to be different than the stat() mtime the first
- X * time this function is called.
- X */
- X if (old_db->mtime == statbuf.st_mtime)
- X {
- X Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n",
- X getpid()))
- X return;
- X }
- X
- X /* make sure the dir is open. only happens the first time, since
- X * the DIR is static and we don't close it. Rewind the dir.
- X */
- X if (dir == NULL)
- X {
- X if (!(dir = opendir(SPOOL_DIR)))
- X {
- X log_it("CROND", getpid(), "OPENDIR FAILED", SPOOL_DIR);
- X (void) exit(ERROR_EXIT);
- X }
- X }
- X (void) rewinddir(dir);
- X
- X /* something's different. make a new database, moving unchanged
- X * elements from the old database, reloading elements that have
- X * actually changed. Whatever is left in the old database when
- X * we're done is chaff -- crontabs that disappeared.
- X */
- X new_db.mtime = statbuf.st_mtime;
- X new_db.head = new_db.tail = NULL;
- X
- X while (NULL != (dp = readdir(dir)))
- X {
- X extern struct passwd *getpwnam();
- X struct passwd *pw;
- X int crontab_fd;
- X char fname[MAXNAMLEN+1],
- X tabname[MAXNAMLEN+1];
- X
- X (void) strncpy(fname, dp->d_name, (int) dp->d_namlen);
- X fname[dp->d_namlen] = '\0';
- X
- X /* avoid file names beginning with ".". this is good
- X * because we would otherwise waste two guaranteed calls
- X * to getpwnam() for . and .., and also because user names
- X * starting with a period are just too nasty to consider.
- X */
- X if (fname[0] == '.')
- X goto next_crontab;
- X
- X if (NULL == (pw = getpwnam(fname)))
- X {
- X /* file doesn't have a user in passwd file.
- X */
- X log_it(fname, getpid(), "ORPHAN", "no passwd entry");
- X goto next_crontab;
- X }
- X
- X sprintf(tabname, CRON_TAB(fname));
- X if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK)
- X {
- X /* crontab not accessible?
- X */
- X log_it(fname, getpid(), "CAN'T OPEN", tabname);
- X goto next_crontab;
- X }
- X
- X if (fstat(crontab_fd, &statbuf) < OK)
- X {
- X log_it(fname, getpid(), "FSTAT FAILED", tabname);
- X goto next_crontab;
- X }
- X
- X Debug(DLOAD, ("\t%s:", fname))
- X u = find_user(old_db, fname);
- X if (u != NULL)
- X {
- X /* if crontab has not changed since we last read it
- X * in, then we can just use our existing entry.
- X * note that we do not check for changes in the
- X * passwd entry (uid, home dir, etc). HINT
- X */
- X if (u->mtime == statbuf.st_mtime)
- X {
- X Debug(DLOAD, (" [no change, using old data]"))
- X unlink_user(old_db, u);
- X link_user(&new_db, u);
- X goto next_crontab;
- X }
- X
- X /* before we fall through to the code that will reload
- X * the user, let's deallocate and unlink the user in
- X * the old database. This is more a point of memory
- X * efficiency than anything else, since all leftover
- X * users will be deleted from the old database when
- X * we finish with the crontab...
- X */
- X Debug(DLOAD, (" [delete old data]"))
- X unlink_user(old_db, u);
- X free_user(u);
- X }
- X u = load_user(
- X crontab_fd,
- X pw->pw_name,
- X pw->pw_uid,
- X pw->pw_gid,
- X pw->pw_dir,
- X pw->pw_shell
- X );
- X if (u != NULL)
- X {
- X u->mtime = statbuf.st_mtime;
- X link_user(&new_db, u);
- X }
- next_crontab:
- X if (crontab_fd >= OK) {
- X Debug(DLOAD, (" [done]\n"))
- X close(crontab_fd);
- X }
- X }
- X /* if we don't do this, then when our children eventually call
- X * getpwnam() in do_command.c's child_process to verify MAILTO=,
- X * they will screw us up (and v-v).
- X *
- X * (this was lots of fun to find...)
- X */
- X endpwent();
- X
- X /* whatever's left in the old database is now junk.
- X */
- X Debug(DLOAD, ("unlinking old database:\n"))
- X for (u = old_db->head; u != NULL; u = u->next)
- X {
- X Debug(DLOAD, ("\t%s\n", env_get(USERENV, u->envp)))
- X unlink_user(old_db, u);
- X free_user(u);
- X }
- X
- X /* overwrite the database control block with the new one.
- X */
- X Debug(DLOAD, ("installing new database\n"))
- X#if defined(BSD)
- X /* BSD has structure assignments */
- X *old_db = new_db;
- X#endif
- X#if defined(ATT)
- X /* ATT, well, I don't know. Use memcpy(). */
- X memcpy(old_db, &new_db, sizeof(cron_db));
- X#endif
- X Debug(DLOAD, ("load_database is done\n"))
- X}
- X
- X
- void
- link_user(db, u)
- X cron_db *db;
- X user *u;
- X{
- X if (db->head == NULL)
- X db->head = u;
- X if (db->tail)
- X db->tail->next = u;
- X u->prev = db->tail;
- X u->next = NULL;
- X db->tail = u;
- X}
- X
- X
- void
- unlink_user(db, u)
- X cron_db *db;
- X user *u;
- X{
- X if (u->prev == NULL)
- X db->head = u->next;
- X else
- X u->prev->next = u->next;
- X
- X if (u->next == NULL)
- X db->tail = u->prev;
- X else
- X u->next->prev = u->prev;
- X}
- X
- X
- user *
- find_user(db, name)
- X cron_db *db;
- X char *name;
- X{
- X char *env_get();
- X user *u;
- X
- X for (u = db->head; u != NULL; u = u->next)
- X if (!strcmp(env_get(USERENV, u->envp), name))
- X break;
- X return u;
- X}
- END_OF_FILE
- if test 6768 -ne `wc -c <'database.c'`; then
- echo shar: \"'database.c'\" unpacked with wrong size!
- fi
- # end of 'database.c'
- fi
- if test -f 'entry.c' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'entry.c'\"
- else
- echo shar: Extracting \"'entry.c'\" \(11561 characters\)
- sed "s/^X//" >'entry.c' <<'END_OF_FILE'
- X#if !defined(lint) && !defined(LINT)
- static char rcsid[] = "$Header: entry.c,v 2.1 90/07/18 00:23:41 vixie Exp $";
- X#endif
- X
- X/* vix 26jan87 [RCS'd; rest of log is in RCS file]
- X * vix 01jan87 [added line-level error recovery]
- X * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
- X * vix 30dec86 [written]
- X */
- X
- X
- X/* Copyright 1988,1990 by Paul Vixie
- X * All rights reserved
- X *
- X * Distribute freely, except: don't remove my name from the source or
- X * documentation (don't take credit for my work), mark your changes (don't
- X * get me blamed for your possible bugs), don't alter or remove this
- X * notice. May be sold if buildable source is provided to buyer. No
- X * warrantee of any kind, express or implied, is included with this
- X * software; use at your own risk, responsibility for damages (if any) to
- X * anyone resulting from the use of this software rests entirely with the
- X * user.
- X *
- X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
- X * I'll try to keep a version up to date. I can be reached as follows:
- X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
- X * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
- X */
- X
- X
- X#include "cron.h"
- X
- typedef enum
- X {e_none, e_minute, e_hour, e_dom, e_month, e_dow, e_cmd, e_timespec}
- X ecode_e;
- static char *ecodes[] =
- X {
- X "no error",
- X "bad minute",
- X "bad hour",
- X "bad day-of-month",
- X "bad month",
- X "bad day-of-week",
- X "bad command",
- X "bad time specifier"
- X };
- X
- void
- free_entry(e)
- X entry *e;
- X{
- X int free();
- X
- X (void) free(e->cmd);
- X (void) free(e);
- X}
- X
- X
- entry *
- load_entry(file, error_func)
- X FILE *file;
- X void (*error_func)();
- X{
- X /* this function reads one crontab entry -- the next -- from a file.
- X * it skips any leading blank lines, ignores comments, and returns
- X * EOF if for any reason the entry can't be read and parsed.
- X *
- X * the entry IS parsed here, btw.
- X *
- X * syntax:
- X * minutes hours doms months dows cmd\n
- X */
- X
- X extern int free();
- X extern char *malloc(), *savestr();
- X extern void unget_char();
- X static char get_list();
- X
- X ecode_e ecode = e_none;
- X entry *e;
- X int ch;
- X void skip_comments();
- X char cmd[MAX_COMMAND];
- X
- X e = (entry *) calloc(sizeof(entry), sizeof(char));
- X
- X Debug(DPARS, ("load_entry()...about to eat comments\n"))
- X
- X skip_comments(file);
- X
- X ch = get_char(file);
- X
- X /* ch is now the first useful character of a useful line.
- X * it may be an @special or it may be the first character
- X * of a list of minutes.
- X */
- X
- X if (ch == '@')
- X {
- X /* all of these should be flagged and load-limited; i.e.,
- X * instead of @hourly meaning "0 * * * *" it should mean
- X * "close to the front of every hour but not 'til the
- X * system load is low". Problems are: how do you know
- X * what "low" means? (save me from /etc/crond.conf!) and:
- X * how to guarantee low variance (how low is low?), which
- X * means how to we run roughly every hour -- seems like
- X * we need to keep a history or let the first hour set
- X * the schedule, which means we aren't load-limited
- X * anymore. too much for my overloaded brain. (vix, jan90)
- X * HINT
- X */
- X ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
- X if (!strcmp("reboot", cmd)) {
- X e->flags |= WHEN_REBOOT;
- X } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
- X bit_set(e->minute, 0);
- X bit_set(e->hour, 0);
- X bit_set(e->dom, 0);
- X bit_set(e->month, 0);
- X bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
- X } else if (!strcmp("monthly", cmd)) {
- X bit_set(e->minute, 0);
- X bit_set(e->hour, 0);
- X bit_set(e->dom, 0);
- X bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
- X bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
- X } else if (!strcmp("weekly", cmd)) {
- X bit_set(e->minute, 0);
- X bit_set(e->hour, 0);
- X bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
- X bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
- X bit_set(e->dow, 0);
- X } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
- X bit_set(e->minute, 0);
- X bit_set(e->hour, 0);
- X bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
- X bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
- X bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
- X } else if (!strcmp("hourly", cmd)) {
- X bit_set(e->minute, 0);
- X bit_set(e->hour, (LAST_HOUR-FIRST_HOUR+1));
- X bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
- X bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
- X bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
- X } else {
- X ecode = e_timespec;
- X goto eof;
- X }
- X }
- X else
- X {
- X Debug(DPARS, ("load_entry()...about to parse numerics\n"))
- X
- X ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
- X PPC_NULL, ch, file);
- X if (ch == EOF)
- X {
- X ecode = e_minute;
- X goto eof;
- X }
- X
- X /* hours
- X */
- X
- X ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
- X PPC_NULL, ch, file);
- X if (ch == EOF)
- X {
- X ecode = e_hour;
- X goto eof;
- X }
- X
- X /* DOM (days of month)
- X */
- X
- X if (ch == '*') e->flags |= DOM_STAR;
- X ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file);
- X if (ch == EOF)
- X {
- X ecode = e_dom;
- X goto eof;
- X }
- X
- X /* month
- X */
- X
- X ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
- X MonthNames, ch, file);
- X if (ch == EOF)
- X {
- X ecode = e_month;
- X goto eof;
- X }
- X
- X /* DOW (days of week)
- X */
- X
- X if (ch == '*') e->flags |= DOW_STAR;
- X ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
- X DowNames, ch, file);
- X if (ch == EOF)
- X {
- X ecode = e_dow;
- X goto eof;
- X }
- X }
- X
- X /* make sundays equivilent */
- X if (bit_test(e->dow, 0) || bit_test(e->dow, 7))
- X {
- X bit_set(e->dow, 0);
- X bit_set(e->dow, 7);
- X }
- X
- X Debug(DPARS, ("load_entry()...about to parse command\n"))
- X
- X /* ch is first character of a command. everything up to the next
- X * \n or EOF is part of the command... too bad we don't know in
- X * advance how long it will be, since we need to malloc a string
- X * for it... so, we limit it to MAX_COMMAND
- X */
- X unget_char(ch, file);
- X ch = get_string(cmd, MAX_COMMAND, file, "\n");
- X
- X /* a file without a \n before the EOF is rude, so we'll complain...
- X */
- X if (ch == EOF)
- X {
- X ecode = e_cmd;
- X goto eof;
- X }
- X
- X /* got the command in the 'cmd' string; save it in *e.
- X */
- X e->cmd = savestr(cmd);
- X
- X Debug(DPARS, ("load_entry()...returning successfully\n"))
- X
- X /* success, fini, return pointer to the entry we just created...
- X */
- X return e;
- X
- eof: /* if we want to return EOF, we have to jump down here and
- X * free the entry we've been building.
- X *
- X * now, in some cases, a parse routine will have returned EOF to
- X * indicate an error, but the file is not actually done. since, in
- X * that case, we only want to skip the line with the error on it,
- X * we'll do that here.
- X *
- X * many, including the author, see what's below as evil programming
- X * practice: since I didn't want to change the structure of this
- X * whole function to support this error recovery, I recurse. Cursed!
- X * (At least it's tail-recursion, as if it matters in C - vix/8feb88)
- X * I'm seriously considering using (another) GOTO... argh!
- X * (this does not get less disgusting over time. vix/15nov88)
- X */
- X
- X (void) free(e);
- X
- X if (feof(file))
- X return NULL;
- X
- X if (error_func)
- X (*error_func)(ecodes[(int)ecode]);
- X do {ch = get_char(file);}
- X while (ch != EOF && ch != '\n');
- X if (ch == EOF)
- X return NULL;
- X return load_entry(file, error_func);
- X}
- X
- X
- static char
- get_list(bits, low, high, names, ch, file)
- X bitstr_t *bits; /* one bit per flag, default=FALSE */
- X int low, high; /* bounds, impl. offset for bitstr */
- X char *names[]; /* NULL or *[] of names for these elements */
- X int ch; /* current character being processed */
- X FILE *file; /* file being read */
- X{
- X static char get_range();
- X register int done;
- X
- X /* we know that we point to a non-blank character here;
- X * must do a Skip_Blanks before we exit, so that the
- X * next call (or the code that picks up the cmd) can
- X * assume the same thing.
- X */
- X
- X Debug(DPARS|DEXT, ("get_list()...entered\n"))
- X
- X /* list = "*" | range {"," range}
- X */
- X
- X if (ch == '*')
- X {
- X /* '*' means 'all elements'.
- X */
- X bit_nset(bits, 0, (high-low+1));
- X goto exit;
- X }
- X
- X /* clear the bit string, since the default is 'off'.
- X */
- X bit_nclear(bits, 0, (high-low+1));
- X
- X /* process all ranges
- X */
- X done = FALSE;
- X while (!done)
- X {
- X ch = get_range(bits, low, high, names, ch, file);
- X if (ch == ',')
- X ch = get_char(file);
- X else
- X done = TRUE;
- X }
- X
- exit: /* exiting. skip to some blanks, then skip over the blanks.
- X */
- X Skip_Nonblanks(ch, file)
- X Skip_Blanks(ch, file)
- X
- X Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
- X
- X return ch;
- X}
- X
- X
- static char
- get_range(bits, low, high, names, ch, file)
- X bitstr_t *bits; /* one bit per flag, default=FALSE */
- X int low, high; /* bounds, impl. offset for bitstr */
- X char *names[]; /* NULL or names of elements */
- X int ch; /* current character being processed */
- X FILE *file; /* file being read */
- X{
- X /* range = number | number "-" number [ "/" number ]
- X */
- X
- X static int set_element();
- X static char get_number();
- X register int i;
- X auto int num1, num2, num3;
- X
- X Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
- X
- X if (EOF == (ch = get_number(&num1, low, names, ch, file)))
- X return EOF;
- X
- X if (ch != '-')
- X {
- X /* not a range, it's a single number.
- X */
- X if (EOF == set_element(bits, low, high, num1))
- X return EOF;
- X }
- X else
- X {
- X /* eat the dash
- X */
- X ch = get_char(file);
- X if (ch == EOF)
- X return EOF;
- X
- X /* get the number following the dash
- X */
- X ch = get_number(&num2, low, names, ch, file);
- X if (ch == EOF)
- X return EOF;
- X
- X /* check for step size
- X */
- X if (ch == '/')
- X {
- X /* eat the slash
- X */
- X ch = get_char(file);
- X if (ch == EOF)
- X return EOF;
- X
- X /* get the step size -- note: we don't pass the
- X * names here, because the number is not an
- X * element id, it's a step size. 'low' is
- X * sent as a 0 since there is no offset either.
- X */
- X ch = get_number(&num3, 0, PPC_NULL, ch, file);
- X if (ch == EOF)
- X return EOF;
- X }
- X else
- X {
- X /* no step. default==1.
- X */
- X num3 = 1;
- X }
- X
- X /* range. set all elements from num1 to num2, stepping
- X * by num3. (the step is a downward-compatible extension
- X * proposed conceptually by bob@acornrc, syntactically
- X * designed then implmented by paul vixie).
- X */
- X for (i = num1; i <= num2; i += num3)
- X if (EOF == set_element(bits, low, high, i))
- X return EOF;
- X }
- X return ch;
- X}
- X
- X
- static char
- get_number(numptr, low, names, ch, file)
- X int *numptr;
- X int low;
- X char *names[];
- X char ch;
- X FILE *file;
- X{
- X char temp[MAX_TEMPSTR], *pc;
- X int len, i, all_digits;
- X
- X /* collect alphanumerics into our fixed-size temp array
- X */
- X pc = temp;
- X len = 0;
- X all_digits = TRUE;
- X while (isalnum(ch))
- X {
- X if (++len >= MAX_TEMPSTR)
- X return EOF;
- X
- X *pc++ = ch;
- X
- X if (!isdigit(ch))
- X all_digits = FALSE;
- X
- X ch = get_char(file);
- X }
- X *pc = '\0';
- X
- X /* try to find the name in the name list
- X */
- X if (names)
- X for (i = 0; names[i] != NULL; i++)
- X {
- X Debug(DPARS|DEXT,
- X ("get_num, compare(%s,%s)\n", names[i], temp))
- X if (!nocase_strcmp(names[i], temp))
- X {
- X *numptr = i+low;
- X return ch;
- X }
- X }
- X
- X /* no name list specified, or there is one and our string isn't
- X * in it. either way: if it's all digits, use its magnitude.
- X * otherwise, it's an error.
- X */
- X if (all_digits)
- X {
- X *numptr = atoi(temp);
- X return ch;
- X }
- X
- X return EOF;
- X}
- X
- X
- static int
- set_element(bits, low, high, number)
- X bitstr_t *bits; /* one bit per flag, default=FALSE */
- X int low;
- X int high;
- X int number;
- X{
- X Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
- X
- X if (number < low || number > high)
- X return EOF;
- X
- X Debug(DPARS|DEXT, ("bit_set(%x,%d)\n",bits,(number-low)))
- X bit_set(bits, (number-low));
- X Debug(DPARS|DEXT, ("bit_set succeeded\n"))
- X return OK;
- X}
- END_OF_FILE
- if test 11561 -ne `wc -c <'entry.c'`; then
- echo shar: \"'entry.c'\" unpacked with wrong size!
- fi
- # end of 'entry.c'
- fi
- echo shar: End of archive 2 \(of 3\).
- cp /dev/null ark2isdone
- MISSING=""
- for I in 1 2 3 ; do
- if test ! -f ark${I}isdone ; then
- MISSING="${MISSING} ${I}"
- fi
- done
- if test "${MISSING}" = "" ; then
- echo You have unpacked all 3 archives.
- rm -f ark[1-9]isdone
- else
- echo You still need to unpack the following archives:
- echo " " ${MISSING}
- fi
- ## End of shell archive.
- exit 0
-
- exit 0 # Just in case...
-